/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.jitag;

import java.io.File;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import sk.jp.commons.exception.ApplicationException;
import sk.jp.commons.tools.Properties;
import sk.jp.commons.ui.ExceptionDialog;
import sk.jp.commons.ui.StatusBar;
import sk.jp.commons.ui.bgproc.BackgroundProcessIndicator;
import sk.jp.commons.ui.bgproc.ProcessFailureHandler;
import sk.jp.commons.ui.bgproc.ProcessStateHandlerForStatusbar;
import sk.jp.commons.utils.BackgroundProcess;
import sk.jp.commons.utils.Cancellable;
import sk.jp.commons.utils.FileUtil;
import sk.jp.commons.utils.LogMsg;
import sk.jp.jitag.dao.JitagDao;
import sk.jp.jitag.exception.CancelledException;
import sk.jp.jitag.model.config.MediaTypeClass;
import sk.jp.jitag.model.config.Profile;
import sk.jp.jitag.proc.GarbageCollectorProcess;
import sk.jp.jitag.ui.MainWindow;
import sk.jp.jitag.ui.PreferencesDialog;
import sk.jp.jitag.util.Msg;
import sk.jp.jitag.util.Property;
import sk.jp.jitag.vo.RepositoryScanStatistics;

/**
 * Central point for process application logic.
 */
@Component
public class ApplicationLogic {

    private static final Logger log = LoggerFactory.getLogger(ApplicationLogic.class);

    @Autowired
    private ConfigManager configManager;

    @SuppressWarnings({"SpringJavaAutowiringInspection"})
    @Autowired
    private JitagDao dao;

    //----------------------------------------------------------------------

    /**
     * Creates system info text in HTML format.
     * @return system info text in HTML format.
     */
    public String buildSystemInfo() {

        //TODO Dokoncit!!! + check app state (DAO, db version ID)
        //TODO Dokoncit!!! show app & db version  & DbVersion props.

        StringBuilder buf = new StringBuilder();
        buf.append("<body style=\"font-family: sans-serif;\">");
        //
        buf.append("<p><b>").append(Msg.appSysPropsBase.value()).append("</b></p>") //Base properties
            .append("<table bgcolor=#d4d3d3 border=0 cellpadding=1 cellspacing=1>");
        for (Property property : Property.values()) {
            buf.append("<tr><td>").append(property.getPropKey()).append("</td>")
                .append("<td>").append(property.value()).append("</td></tr>");
        }
        buf.append("</table>");
        //
        Map<String, Profile> profiles = configManager.getConfig().getProfiles();
        buf.append("<p><b>").append(Msg.appSysPropsProfiles.value()).append("</b></p>"); //Configuration profiles
        for (Map.Entry<String, Profile> profileEntry : profiles.entrySet()) {
            buf.append("<p>").append(profileEntry.getValue().toString()).append("</p>");
        }
        //
        buf.append("<p><b>").append(Msg.appSysPropsSys.value()).append("</b></p>") //Java Runtime Environment properties
            .append("<table bgcolor=#d1dcf3 border=0 cellpadding=1 cellspacing=1>");
        java.util.Properties properties = System.getProperties();
        List pKList = new LinkedList(properties.keySet());
        Collections.sort(pKList);
        for (int i = 0; i < pKList.size(); i++) {
            Object k = pKList.get(i);
            buf.append("<tr><td>").append(k).append("</td><td>")
                .append(properties.get(k)).append("</td></tr>");
        }
        buf.append("</table>");
        //
        buf.append("</body>");
        String systemInfoText = buf.toString();
        return systemInfoText;
    }

    /**
     * Start execution of garbage collector background process.
     * @param backgroundProcessIndicator
     * @param statusBar
     */
    public void runGarbageCollector(BackgroundProcessIndicator backgroundProcessIndicator,
                                           StatusBar statusBar){
        BackgroundProcess.StateHandler stateHandler
            = new ProcessStateHandlerForStatusbar(statusBar);
        GarbageCollectorProcess process
            = new GarbageCollectorProcess(backgroundProcessIndicator,
                                          stateHandler,
                                          new ProcessFailureHandler());
        BackgroundProcessIndicator.DefaultClickHandler clickHandler
            = new BackgroundProcessIndicator.DefaultClickHandler(
                process, backgroundProcessIndicator);
        backgroundProcessIndicator.start(process, clickHandler);
    }

    /**
     * Opens preferences dialog and saves changed configurations.
     * @param mainWindow
     * @return true if configuration was changed, false if user cancell dialog.
     */
    public boolean openPreferencesDialog(MainWindow mainWindow){
        try {
            PreferencesDialog preferencesDialog = new PreferencesDialog(mainWindow,
                                                                configManager.getConfig(),
                                                                dao);
            preferencesDialog.open();

            Map<Property,String> newProperties = preferencesDialog.getNewProperties();
            Profile newProfile = preferencesDialog.getNewProfile();

            if(log.isTraceEnabled()){
                log.trace(LogMsg.toString("Preferences dialog closed and returns",
                                          "newProperties", newProperties,
                                          "newProfile", newProfile));
            }

            if(newProperties != null && newProfile != null){
                Dimension size = mainWindow.getSize();
                Point location = mainWindow.getLocation();
                Rectangle windowPosition = new Rectangle(location.x, location.y, size.width, size.height);
                newProfile.setWindowPosition(windowPosition);

                for (Property property : newProperties.keySet()) {
                    Properties.setProperty(property.getPropKey(), newProperties.get(property));
                }

                configManager.saveBaseProperties();
                File confFile = configManager.resolveConfFilePath(Property.confPlace.value(),
                                                                  Property.confPath.value());
                configManager.saveConfig(confFile);
                return true;
            }else{
                //reload config from persistence
                configManager.loadConfig(Property.confPlace.value(),
                                         Property.confPath.value());
                return false;
            }
        }finally {
            dao.close();
        }
    }

    /**
     * Performs repositories scanning, initiated from given background process.
     * @param cancellable cancellable object that can be checked for cancell operation.
     * @return result statistics if scanning was done successfuly,
     * or null if process was cancelled.
     */
    public RepositoryScanStatistics scanRepositories(Cancellable cancellable){
        log.debug(LogMsg.inp());

        Profile profile = configManager.getConfig().getCurrentProfile();
        List<String> repositoryLocations = profile.getRepositoryLocations();
        List<File> dirs = FileUtil.getAllDirs(new LinkedHashSet<String>(repositoryLocations),
                                              cancellable);

        log.trace(LogMsg.toString("All directories", "dirs", dirs));

        if(cancellable.isCancelled()){
            log.info("Scanning repositories cancelled");
            return null;
        }

        Set<String> fileExtensions = profile.getFileExtensions();
        FileUtil.FileExtensionFilter fileExtensionFilter =
            new FileUtil.FileExtensionFilter(fileExtensions);

        int newCount = 0;
        int updatedCount = 0;
        int deletedCount = 0;
        int nochangeCount = 0;

        dao.beginTransaction();
        try {
            dao.markFilesForDelete(fileExtensions);  //TODO Problem: Ak mam nascanovane files s extension E a potom tu extension vyhodim z profilu, ostanu files s ext E v db, lebo sa ani nemarknu for delete

            for (File dir : dirs) {
                List<File> files = FileUtil.getFiles(dir, fileExtensionFilter);
                for (File file : files) {

                    if(cancellable.isCancelled()){
                        log.info("Scanning repositories cancelled");
                        throw new CancelledException();
                    }

                    Integer fileId = FileUtil.getFileId(file);
                    Integer fileHash = FileUtil.getFileHash(file);

                    JitagDao.FileSyncState syncState = dao.isChanged(fileId, fileHash);
                    if(syncState == JitagDao.FileSyncState.equals){
                        dao.unmarkFileForDeleteFlag(fileId);
                        nochangeCount++;
                    }else if(syncState == JitagDao.FileSyncState.changed){
                        dao.updateChangedFile(createFileEntity(file, fileId, fileHash));
                        dao.unmarkFileForDeleteFlag(fileId);
                        updatedCount++;
                    }else if(syncState == JitagDao.FileSyncState.missing){
                        dao.saveNewFile(createFileEntity(file, fileId, fileHash));
                        dao.unmarkFileForDeleteFlag(fileId);
                        newCount++;
                    }
                }

            }

            deletedCount = dao.deleteMarkedFiles();
            dao.commitTransaction();
        } catch (CancelledException ce) {
            dao.rollbackTransaction(ce);
            return null;
        } catch (Throwable e) {
            dao.rollbackTransaction(e);
            throw new ApplicationException("Scanning file repositories failed",
                                           null, null, e);
        } finally {
            dao.close();
        }

        //TODO test
        dao.beginTransaction();
        List<sk.jp.jitag.model.File> files = dao.getAll(sk.jp.jitag.model.File.class);
        int d = dao.deleteMarkedFiles();
        List<sk.jp.jitag.model.File> files2 = dao.getAll(sk.jp.jitag.model.File.class);
        dao.commitTransaction();
        dao.close();

        RepositoryScanStatistics stat = new RepositoryScanStatistics(newCount,
                                             updatedCount, deletedCount, nochangeCount);
        log.trace(LogMsg.out("stat", stat));
        return stat;
    }

    /**
     * Performs pre-exit operations: save config properties, dao close.
     * @param mainWindow
     */
    public void exitApplication(MainWindow mainWindow) {
        //Save properties
        try {
            configManager.saveBaseProperties();
            configManager.saveConfig();
        } catch (Exception e) {
            log.error("Unexpected exception", e);
            try {
                ExceptionDialog.show(mainWindow, e);
            } catch (Throwable e1) {
                log.error("Unexpected exception during show error message dialog", e1);
            }
        }

        //Closing operations
        try {
            dao.close();
        } catch (Exception e) {
            log.error("Can't close database", e);
        }

        //Close application
        log.debug("Exiting application");
        //noinspection CallToSystemExit
        System.exit(0);
    }

    /**
     * Initialize database enumerations if they are not initialized before.
     */
    public void initEnums(){
        dao.doInTransaction(new Runnable() {
            public void run() {
                dao.getOrNew(MediaTypeClass.Items.image.name(), MediaTypeClass.class,
                             new MediaTypeClass(MediaTypeClass.Items.image));
                dao.getOrNew(MediaTypeClass.Items.text.name(), MediaTypeClass.class,
                             new MediaTypeClass(MediaTypeClass.Items.text));
                dao.getOrNew(MediaTypeClass.Items.video.name(), MediaTypeClass.class,
                             new MediaTypeClass(MediaTypeClass.Items.video));
                dao.getOrNew(MediaTypeClass.Items.audio.name(), MediaTypeClass.class,
                             new MediaTypeClass(MediaTypeClass.Items.audio));
                dao.getOrNew(MediaTypeClass.Items.other.name(), MediaTypeClass.class,
                             new MediaTypeClass(MediaTypeClass.Items.other));
            }
        });
    }

    //----------------------------------------------------------------------

    private sk.jp.jitag.model.File createFileEntity(File file, Integer fileId, Integer fileHash){
        String name = file.getName();
        String extension = FileUtil.getFileExtension(name);
        return new sk.jp.jitag.model.File(fileId, fileHash, file.getAbsolutePath(), //TODO Dokoncit!!! nie abs path, ale iba abs path adresara!
                                          name, extension);
    }

}
